require 'rubygems'
require 'socket'
require 'mongrel'
require 'active_support'
require 'net/http'
require 'uri'
require 'timeout'
require 'yaml'
require 'esi/cache'
require 'esi/config'
require 'esi/http_header'
require 'esi/router'
require 'esi/invalidator'
#DEBUG=1
require 'esi/debug' if defined?(DEBUG) and DEBUG == 1

module ESI

  class Handler < Mongrel::HttpHandler
    attr_reader :config

    Thread.abort_on_exception = false

    def initialize( root, options = {} )
      super()
      @config = ESI::Config.new(options[:config_file] || "#{root}/config/esi.yml")
      @router = Router.new( root, @config )
      @parser = @config.parser(@router,@config.cache)
      if @config.start_invalidator?
        ESI::Invalidator.start( self )
      end

      $logger.debug "\n** Loading ESI parser...\n - #{@parser.class}\n"
    end

    # this tells mongrel to call request_begins
    def request_notify
      true
    end

    def request_begins(params)
      @start = Time.now
      @status = 200
      path = params["REQUEST_URI"]

      @url = @router.url_for(path)

      http_params(params)

      @proxy_error = nil

    end

    def request_progress(params, clen, total)
      #puts "progressing... #{clen} of #{total}"
    end

    def process(request, response)

      $logger.debug "#{request.params["REQUEST_METHOD"]} => #{@url}"
      tries = 0
			@uri = URI.parse(@url)

			path_with_query = @uri.query.blank? ? @uri.path : "#{@uri.path}?#{@uri.query}"

			@proxy_request = (request.params["REQUEST_METHOD"] == "POST") ?
													Net::HTTP::Post.new( path_with_query, @params ) :
													Net::HTTP::Get.new( path_with_query, @params )

			@proxy_connection = Net::HTTP.start(@uri.host, @uri.port)

			# open the conneciton up so we can start to stream the connection
			@proxy_connection.request(@proxy_request,request.body.read) do|proxy_response|

				@status = read_status( proxy_response )

				copy_headers( response.header, proxy_response ) unless @status >= 500

				if @status >= 500 or !@config.enable_esi_processor( proxy_response )
					response.start(@status, true) do|head,out|

						if @status >= 500
							# TODO: only report this if configured to expose it
							out << @proxy_error
						end

						# proxy the 500 response
						proxy_response.read_body do|fragment|
							out << fragment
						end

					end
				else
					begin
						# we detected the surrogate wants us to process the response checking for ESI 
						# tags.

						response.header["Transfer-Encoding"] = "chunked"
						# this is the important part, rather then send the whole document back we send in chunks
						# each fragment is it's own chunk, this does mean we require http 1.1
						header = Mongrel::Const::STATUS_FORMAT % [@status, Mongrel::HTTP_STATUS_CODES[@status]]
						header.gsub!(/Connection: close\r\n/,'')
						response.header.out.rewind
						header << response.header.out.read + Mongrel::Const::LINE_END
						header.gsub!(/Status:.*?\r\n/,'')
						response.write( header )

						#puts header

						@parser.process_io( :input_stream => proxy_response,
																:response_headers => response.header,
																:request => request,
																:http_params => @params ) do |buffer|
							# send a new chunk
							size = buffer.size
							chunk_header = "#{"%x" % size}" + Mongrel::Const::LINE_END
							#puts chunk_header.inspect
							response.write( chunk_header )  # write the chunk size
							#puts buffer.inspect
							response.write( buffer + Mongrel::Const::LINE_END )  # write the chunk
						end
					rescue => e
						response.write( error_response(e,@url) )
					end
					response.write( "0\r\n\r\n" )
					response.done = true
				end
			end # end request

		rescue Timeout::Error => timeout
			tries += 1
			retry if tries < 4
		rescue => e
			error_response(e,@url)
		ensure

      $request_logger.debug "\nCompleted => #{@url}, #{Time.now - @start} seconds with status #{@status}"

    end

protected

    def error_response(e,url)
      @status = 500
      error = "<h1>Internal Server Errror</h1><h4>Failed while requesting => '#{url}'</h4>\n<pre>#{e.message}#{e.backtrace.join("\n")}</pre>"
      $logger.error e.backtrace
      $logger.error error
      "<html><body>#{error}</body></html>"
    end

    def read_status(response)
      Net::HTTPResponse::CODE_TO_OBJ.select { |k,v| v == response.class }.first[0].to_i rescue 500
    end

    def http_params(params)
      @params = {}
      params.each do|k,v|
        k = k.split('_').collect { |t| t.humanize }.join('-')
        if k.match(/^Http-/)
          k.gsub!(/^Http-/,'')
          @params[k] = v
        end
      end
    end

    def copy_headers(head,response)
      # copy the response headers, except Content-Length let HttpResponse set that,
      # since it's likely going to increase with esi requests
      response.each_capitalized do |k,v|
        # for Set-Cookie we need to split on ,
        if k == "Set-Cookie"
          v.split(/,/).each do|cookie|
            head["Set-Cookie"] = cookie # mongrel is case sensitive about handling duplicates
          end
        else
          head[k] = v unless k == "Content-Length" or k == "Surrogate-Control"
        end
      end
    end

  end # Handler

end # ESI
